package ch.ti.bfh.app.loodel.internal;

import ch.ti.bfh.app.loodel.domain.learningEvent.LearningEventRepository;
import ch.ti.bfh.app.loodel.domain.member.Member;
import ch.ti.bfh.app.loodel.domain.timeslot.QTimeSlot;
import ch.ti.bfh.app.loodel.domain.timeslot.TimeSlot;
import ch.ti.bfh.app.loodel.domain.timeslotvote.QTimeSlotVote;
import ch.ti.bfh.app.loodel.domain.timeslotvote.TimeSlotVote;
import ch.ti.bfh.app.loodel.domain.timeslotvote.TimeSlotVoteRepository;
import ch.ti.bfh.app.loodel.internal.security.InternalUserContext;
import ch.ti.bfh.app.loodel.service.timeslotschedule.MemberTimeSlotScheduleService;
import ch.ti.bfh.app.loodel.service.timeslotschedule.TimeSlotScheduleData;
import ch.ti.bfh.app.loodel.service.timeslotschedule.TimeSlotScheduleSearchCriteria;
import ch.ti.bfh.app.loodel.service.vote.LearningEventVoteData;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.jpa.impl.JPAQuery;
import com.mysema.query.jpa.impl.JPASubQuery;
import com.mysema.query.types.expr.BooleanExpression;
import com.mysema.query.types.query.ListSubQuery;
import org.apache.commons.lang.Validate;
import org.joda.time.LocalTime;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.List;

@Service
@Transactional
public class DefaultMemberTimeSlotScheduleService implements MemberTimeSlotScheduleService {

    @PersistenceContext
    private EntityManager entityManager;

    private final InternalUserContext userContext;

    private final TimeSlotVoteRepository timeSlotVoteRepository;

    private final LearningEventRepository learningEventRepository;

    @Inject
    public DefaultMemberTimeSlotScheduleService(InternalUserContext userContext, TimeSlotVoteRepository timeSlotVoteRepository, LearningEventRepository learningEventRepository) {
        this.userContext = userContext;
        this.timeSlotVoteRepository = timeSlotVoteRepository;
        this.learningEventRepository = learningEventRepository;
    }

    @Override
    public List<TimeSlotScheduleData> searchTimeSlotsByCriteria(TimeSlotScheduleSearchCriteria timeSlotScheduleSearchCriteria) {
        Validate.notNull(timeSlotScheduleSearchCriteria.getStartDateRange());
        Validate.notNull(timeSlotScheduleSearchCriteria.getEndDateRange());
        final Long currentMemberId = userContext.getCurrentMember().getId();

        final QTimeSlot timeSlot = QTimeSlot.timeSlot;
        final BooleanBuilder dynamicBooleanBuilder = new BooleanBuilder();

        final BooleanExpression withinDateRange = dateRangeExpression(timeSlotScheduleSearchCriteria, timeSlot);

        if (timeSlotScheduleSearchCriteria.isShowOnlyAcceptedTimeSlots()) {
            dynamicBooleanBuilder.and(timeSlot.takingPlace.eq(true));
        }
        if (timeSlotScheduleSearchCriteria.isShowOnlyClosedLearningEventTimeSlots()) {
            dynamicBooleanBuilder.and(timeSlot.learningEvent.votesClosed.eq(true));
        }
        if (timeSlotScheduleSearchCriteria.isShowOnlyMyVotedTimeSlots()) {
            final QTimeSlotVote timeSlotVote = new QTimeSlotVote("timeSlotVoteSub");
            final QTimeSlot timeSlotVoteTimeSlot = timeSlotVote.timeSlot;
            final JPASubQuery jpaSubQuery = buildTimeSlotSubQueryBasedOnCurrentMemberTimeSlotVotes(timeSlotScheduleSearchCriteria, currentMemberId, timeSlotVote, timeSlotVoteTimeSlot);
            final ListSubQuery<Long> currentMemberTimeSlotVotes = jpaSubQuery.distinct().list(timeSlotVoteTimeSlot.id);
            dynamicBooleanBuilder.and(timeSlot.id.in(currentMemberTimeSlotVotes));
        } else {
            final QTimeSlotVote timeSlotVote = QTimeSlotVote.timeSlotVote;
            final QTimeSlot timeSlotVoteTimeSlot = timeSlotVote.timeSlot;
            final JPASubQuery jpaSubQuery = buildTimeSlotSubQueryBasedOnCurrentMemberTimeSlotVotes(timeSlotScheduleSearchCriteria, currentMemberId, timeSlotVote, timeSlotVoteTimeSlot);
            final ListSubQuery<String> currentMemberVotedLearningEvents = jpaSubQuery.distinct().list(timeSlotVote.timeSlot.learningEvent.id);
            dynamicBooleanBuilder.and(timeSlot.learningEvent.id.in(currentMemberVotedLearningEvents));
        }
        JPAQuery query = new JPAQuery(entityManager).from(timeSlot).where(withinDateRange.and(dynamicBooleanBuilder));
        final List<TimeSlot> list = query.distinct().list(timeSlot);
        List<TimeSlotScheduleData> resultList = new ArrayList<TimeSlotScheduleData>();
        for (TimeSlot slot : list) {
            TimeSlotScheduleData timeSlotScheduleData = new TimeSlotScheduleData();
            timeSlotScheduleData.setTimeSlotData(TimeSlotAssembler.getTimeSlotData(slot));
            timeSlotScheduleData.setTitle(slot.getTimeSlotLongTitle());
            timeSlotScheduleData.setLearningEventId(slot.getLearningEvent().getId());
            if (slot.getLearningEvent().getVotesClosed() == false) {
                timeSlotScheduleData.setAccepted(null);
            } else {
                timeSlotScheduleData.setAccepted(slot.getTakingPlace());
            }
            final TimeSlotVote timeSlotVote = timeSlotVoteRepository.findByTimeSlotIdAndMemberId(slot.getId(), currentMemberId);
            if (timeSlotVote != null) {
                timeSlotScheduleData.setCurrentMemberVoted(true);
            }
            resultList.add(timeSlotScheduleData);
        }
        return resultList;
    }

    @Override
    public LearningEventVoteData getLearningEventVoteData(String learningEventId) {
        List<Member> allVotedMembersForLearningEvent = timeSlotVoteRepository.findAllVotedMembersForLearningEvent(learningEventId);
        LearningEventVoteData learningEventVoteData = LearningEventAssembler.getLearningEventVoteData(learningEventRepository.getOne(learningEventId), allVotedMembersForLearningEvent.size());
        return learningEventVoteData;
    }


    private JPASubQuery buildTimeSlotSubQueryBasedOnCurrentMemberTimeSlotVotes(TimeSlotScheduleSearchCriteria timeSlotScheduleSearchCriteria, Long currentMemberId, QTimeSlotVote timeSlotVote, QTimeSlot timeSlot) {
        return new JPASubQuery().from(timeSlotVote).where(currentMemberExpression(currentMemberId, timeSlotVote).and(dateRangeExpression(timeSlotScheduleSearchCriteria, timeSlot)));
    }

    private BooleanExpression currentMemberExpression(Long currentMemberId, QTimeSlotVote timeSlotVote) {
        return timeSlotVote.voter.id.eq(currentMemberId);
    }

    private BooleanExpression dateRangeExpression(TimeSlotScheduleSearchCriteria timeSlotScheduleSearchCriteria, QTimeSlot timeSlot1) {
        return timeSlot1.startDateTime.goe(timeSlotScheduleSearchCriteria.getStartDateRange().toLocalDateTime(LocalTime.MIDNIGHT)).and(timeSlot1.endDateTime.loe(timeSlotScheduleSearchCriteria.getEndDateRange().plusDays(1).toLocalDateTime(LocalTime.MIDNIGHT)));
    }
}
